package com.openatc.optimize.fixedtimeplan.model.control;


import com.alibaba.fastjson.JSON;
import com.openatc.optimize.fixedtimeplan.config.cross.*;

import com.openatc.optimize.fixedtimeplan.enums.OptTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

/**
 * @author mz
 * @version 1.0
 * @date 2021/10/26 8:52
 */
@Data
@AllArgsConstructor
@Slf4j
@Builder
public class Cross {





    public FixedtimePlan optimize(){
        OptTypeEnum optTypeEnum = OptTypeEnum.getOptTypeEnumByValue(crossConfig.getType());
        if(optTypeEnum == OptTypeEnum.CYCLE_OPT){
            adjustPhaseDurationAssignEqually(crossConfig.getOptcycle());
            return createFixedtimePlan();
        }else{
            return calcFixedtimePlan();
        }
    }


    private Map<Integer, PhaseOpt> phaseMap;
    private String agentid;
    private String unitid;

    private double totalY;
    private long cycle;



    //路口初始化配置
    private CrossConfig crossConfig;


    public void updatePhaseMap(){
        for(PhaseOpt phase:crossConfig.getPhases()){
            phaseMap.put(phase.getId(), phase);
        }

        for(int id:crossConfig.getRings().get(0).getSequence()){
            this.cycle += phaseMap.get(id).getDuration();

        }
        if(cycle != 0){
            for(PhaseOpt phase:phaseMap.values()){
                phase.setSplitratio(1.0 * phase.getDuration() / this.cycle);
            }

        }

    }

    public void adjustPhaseDurationAssignEqually(long cycle){
        for(Ring ring:crossConfig.getRings()){
            this.cycle = 0;
            for(int id:ring.getSequence()){
                phaseMap.get(id).setDuration(Math.round(cycle * phaseMap.get(id).getSplitratio()));
                this.cycle += phaseMap.get(id).getDuration();
            }
        }
        fixminoroffset(cycle);
    }

    private double updateTotalY(){
        double y = 0;
        for(Barrier barrier:crossConfig.getBarriers()){
            calcYofBarrier(barrier);
            log.info("y of barrer {} is {}", barrier.getBarrier(), barrier.getY());
            y += barrier.getY();
        }
        return y;
    }

    private void calcYofBarrier(Barrier barrier){
        double Y = 0;
        for(BarrierItem item:barrier.getItems()){
            List<Integer> ids = item.getData();
            double tempY = 0;
            for(Integer id:ids){
                tempY += phaseMap.get(id).Y();
            }
            log.info("temp Y of barrer {} is {}", barrier.getBarrier(), tempY);
            if(tempY > Y){
                Y = tempY;
            }
        }

        /*
        for(List<Integer> ids:barrier.getPhases()){
            double tempY = 0;
            for(Integer id:ids){
                tempY += phaseMap.get(id).Y();
            }
            log.info("temp Y of barrer {} is {}", barrier.getNum(), tempY);
            if(tempY > Y){
                Y = tempY;
            }
        }

         */
        barrier.setY(Y);
    }

    private void assignDurationOfBarrier(){
        for(Barrier barrier:crossConfig.getBarriers()){
            barrier.setDuration(Math.round(barrier.getY()/totalY*cycle));
            log.info("duration of barrier {} is {}", barrier.getBarrier(), barrier.getDuration());
            for(BarrierItem item:barrier.getItems()){
                List<Integer> ids = item.getData();
                double tempY = 0;
                for(Integer id : ids){
                    tempY += phaseMap.get(id).Y();
                }
                for(Integer id : ids){
                    phaseMap.get(id).setDuration(Math.round(barrier.getDuration()*phaseMap.get(id).Y()/tempY));
                }
            }

            /**
             * 根据最大最小绿调整相位长度
             * */
            barrier.adjustPhaseDuration(phaseMap);

            /*
            for(List<Integer> ids:barrier.getPhases()){
                double tempY = 0;
                for(Integer id : ids){
                    tempY += phaseMap.get(id).Y();
                }
                for(Integer id : ids){
                    phaseMap.get(id).setDuration(Math.round(barrier.getDuration()*phaseMap.get(id).Y()/tempY));
                }

            }

             */

        }
    }


    public FixedtimePlan calcFixedtimePlan(){


        totalY = updateTotalY();

        log.info("Y of agent:{} is {}", agentid, totalY);
        double L = (crossConfig.getLoss()+crossConfig.getRed()) * crossConfig.getRings().get(0).getSequence().size();
        log.info("loss time of agentid: {} is {}", agentid, L);


        totalY  = totalY > 0.95 ? 0.95 : totalY;
        if(totalY < 0.9){

            //webster模型
            cycle = Math.round((1.5 * L + 5) / (1 - totalY));

        }else{

            //ARRB模型
            cycle = Math.round((1.4 * L + 6) / (1- totalY));
        }

        log.info("cycle of {} is {}", agentid, cycle);


        assignDurationOfBarrier();



        log.info("final cycle of {} is {}", agentid, cycle);



        fixminoroffset();



        return createFixedtimePlan();
    }


    public FixedtimePlan createFixedtimePlan(){
        return FixedtimePlan.builder()
                .agentid(agentid)
                .cycle(Math.round(cycle))
                .rings(this.crossConfig.getRings())
                .barriers(crossConfig.getBarriers())
                .phase(new ArrayList<>(phaseMap.values()))
                .build();
    }



    public void fixminoroffset(){
        for(Barrier barrier:crossConfig.getBarriers()){
            barrier.fixMinorOffset(phaseMap);
        }
        for(Ring ring:crossConfig.getRings()){
            this.cycle = 0;
            for(int id:ring.getSequence()){
                this.cycle += phaseMap.get(id).getDuration();
            }
        }
    }

    public void fixminoroffset(long cycle){

        for(Barrier barrier:crossConfig.getBarriers()){
            barrier.fixMinorOffset(phaseMap);
        }
        for(Ring ring:crossConfig.getRings()){
            this.cycle = 0;
            for(int id:ring.getSequence()){
                this.cycle += phaseMap.get(id).getDuration();
            }
        }

        long cycleoffset = cycle - this.cycle;

        log.info("rings:{}", crossConfig.getRings());
        for(Ring ring:crossConfig.getRings()){
            int targetid = ring.getSequence().get(0);
            phaseMap.get(targetid).setDuration(phaseMap.get(targetid).getDuration() + cycleoffset);
        }
        this.cycle = cycle;
    }

    public void adjustPhaseDurationAssignToCophsae(long cycle){

        int cophaseid = 1;
        log.info("rings:{}", crossConfig.getRings());
        long diff = cycle-this.cycle;
        for(Ring ring:crossConfig.getRings()){
            if(ring.getSequence().contains(cophaseid)){
                for(int id:ring.getSequence()){
                    if(id == cophaseid){
                        phaseMap.get(id).setDuration(phaseMap.get(id).getDuration() + diff);
                    }
                    phaseMap.get(id).setSplitratio(1.0 * phaseMap.get(id).getDuration() / cycle);
                }
            }
        }
        log.info("adjustPhaseDurationAssignToCophsae phasemap:{}", JSON.toJSONString(phaseMap));

        for(Barrier barrier:crossConfig.getBarriers()){
            for(BarrierItem barrierItem:barrier.getItems()){
                if(barrierItem.getData().contains(cophaseid)){
                    barrierItem.setDuration(barrierItem.getDuration() + diff);
                    barrier.setDuration(barrier.getDuration() + diff);

                }
            }
        }
        log.info("adjustPhaseDurationAssignToCophsae barriers:{}",JSON.toJSONString(crossConfig.getBarriers()));
        for(Barrier barrier:crossConfig.getBarriers()){
            for(BarrierItem barrierItem:barrier.getItems()){
                barrierItem.adjustPhaseDuration(phaseMap, barrier.getDuration());
            }
        }
        for(Ring ring:crossConfig.getRings()){
            this.cycle = 0;
            for(int id:ring.getSequence()){
                this.cycle += phaseMap.get(id).getDuration();
            }
        }


        fixminoroffset(cycle);

    }

    /**
     * 最大最小绿调整后，重新计算周期长
     * */
    private void updateCycle(){
        int tempcycle = 0;
        for(Barrier barrier:crossConfig.getBarriers()){
            tempcycle += barrier.getDuration();
        }
        cycle = tempcycle;
    }

}
